sign: Support generic "spki" type of commit signatures
authorDaiki Ueno <dueno@redhat.com>
Fri, 12 Jul 2024 03:54:13 +0000 (12:54 +0900)
committerDaiki Ueno <dueno@redhat.com>
Tue, 18 Mar 2025 07:22:16 +0000 (16:22 +0900)
The current "ed25519" signing type assumes raw Ed25519 key format for
both public and private keys. This patch generalizes it by adding a
new signature type "spki" which uses the X.509 SubjectPublicKeyInfo
format for public keys. Keys in this format can easily be created with
openssl tools and provide crypto agility[1] as the format embeds
algorithm identifier.

The supposed use-case of this feature is to attach multiple signatures
with different algorithms to a single commit, so even if an algorithm
turned vulnerable, the signatures made with other algorithms can still
be used as a fallback. For instance, signer can create an Ed25519
signature along with a quantum-resistent ML-DSA signature.

The following are a couple of implementation notes:

- The private keys shall be stored in the PKCS#8 format, though future
  extensions may support other format such as opaque key handles on a
  hardware token.

- The "spki" signature type prefers the keys to be encoded in the PEM
  format on disk, while it still accepts base64 encoded keys when given
  through the command-line.

1. https://en.wikipedia.org/wiki/Cryptographic_agility

Signed-off-by: Daiki Ueno <dueno@redhat.com>
18 files changed:
Makefile-libostree.am
Makefile-otcore.am
Makefile-tests.am
configure.ac
man/ostree-commit.xml
man/ostree-sign.xml
rust-bindings/sys/tests/constant.c
src/libostree/ostree-sign-spki.c [new file with mode: 0644]
src/libostree/ostree-sign-spki.h [new file with mode: 0644]
src/libostree/ostree-sign.c
src/libostree/ostree-sign.h
src/libotcore/otcore-spki-verify.c [new file with mode: 0644]
src/libotcore/otcore.h
tests/libtest.sh
tests/test-signed-commit-dummy.sh [new file with mode: 0755]
tests/test-signed-commit-ed25519.sh [new file with mode: 0755]
tests/test-signed-commit-spki.sh [new file with mode: 0755]
tests/test-signed-commit.sh [deleted file]

index 701653ed1ce40b00827515a4c9bdbb6bc44232be..e6c1e5537daa92bac7cb7ff8826cbeae62313666 100644 (file)
@@ -261,6 +261,8 @@ libostree_1_la_SOURCES += \
        src/libostree/ostree-sign-dummy.h \
        src/libostree/ostree-sign-ed25519.c \
        src/libostree/ostree-sign-ed25519.h \
+       src/libostree/ostree-sign-spki.c \
+       src/libostree/ostree-sign-spki.h \
        src/libostree/ostree-sign-private.h \
        src/libostree/ostree-blob-reader.c \
        src/libostree/ostree-blob-reader.h \
index 8252ead00ee3a0dbfb7cfa985fbf56cbac9e2a96..ec34578506a4f81b84b41aab363238dc201587db 100644 (file)
@@ -19,6 +19,7 @@ libotcore_la_SOURCES = \
        src/libotcore/otcore.h \
     src/libotcore/otcore-ed25519-verify.c \
     src/libotcore/otcore-prepare-root.c \
+    src/libotcore/otcore-spki-verify.c \
        $(NULL)
 
 libotcore_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/libglnx -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(OT_INTERNAL_GIO_UNIX_CFLAGS) $(OT_INTERNAL_GPGME_CFLAGS) $(OT_DEP_CRYPTO_LIBS) $(LIBSYSTEMD_CFLAGS)
index 415637a5d1af10a522e2e5d47bcfb1577c2058e9..46ca848051a74eeb76f6f51780d7fd9bd5bb1dc9 100644 (file)
@@ -156,12 +156,24 @@ _installed_or_uninstalled_test_scripts = \
        tests/test-summary-collections.sh \
        tests/test-pull-collections.sh \
        tests/test-config.sh \
-       tests/test-signed-commit.sh \
+       tests/test-signed-commit-dummy.sh \
        tests/test-signed-pull.sh \
        tests/test-pre-signed-pull.sh \
        tests/test-signed-pull-summary.sh \
        $(NULL)
 
+if HAVE_ED25519
+_installed_or_uninstalled_test_scripts += \
+       tests/test-signed-commit-ed25519.sh \
+       $(NULL)
+endif
+
+if HAVE_SPKI
+_installed_or_uninstalled_test_scripts += \
+       tests/test-signed-commit-spki.sh \
+       $(NULL)
+endif
+
 if USE_GPGME
 _installed_or_uninstalled_test_scripts += \
        tests/test-remote-gpg-import.sh \
index 70873a2096e50a54b9f87895d7097ff856cec5af..5cedc542293a3a4e30f8dddcf0452a364656f84b 100644 (file)
@@ -484,10 +484,19 @@ if test x$with_openssl != xno; then OSTREE_FEATURES="$OSTREE_FEATURES openssl";
 AM_CONDITIONAL(USE_OPENSSL, test $with_openssl != no)
 dnl end openssl
 
-if test x$with_openssl != xno || test x$with_ed25519_libsodium != xno; then
+AM_CONDITIONAL([HAVE_ED25519], [test x$with_openssl != xno || test x$with_ed25519_libsodium != xno])
+
+AM_COND_IF([HAVE_ED25519], [
    AC_DEFINE([HAVE_ED25519], 1, [Define if ed25519 is supported ])
    OSTREE_FEATURES="$OSTREE_FEATURES sign-ed25519"
-fi
+])
+
+AM_CONDITIONAL([HAVE_SPKI], [test x$with_openssl != xno])
+
+AM_COND_IF([HAVE_SPKI], [
+   AC_DEFINE([HAVE_SPKI], 1, [Define if spki is supported ])
+   OSTREE_FEATURES="$OSTREE_FEATURES sign-spki"
+])
 
 dnl begin gnutls; in contrast to openssl this one only
 dnl supports --with-crypto=gnutls
@@ -732,7 +741,7 @@ echo "
     systemd:                                      $with_libsystemd
     libmount:                                     $with_libmount
     libsodium (ed25519 signatures):               $with_ed25519_libsodium
-    openssl (ed25519 signatures):                 $with_openssl
+    openssl (ed25519 and spki signatures):        $with_openssl
     libarchive (parse tar files directly):        $with_libarchive
     static deltas:                                yes (always enabled now)
     O_TMPFILE:                                    $enable_otmpfile
index 12f4fd10fac6e514918a6a142fbe425cc17e397d..013ec3aadb7dcc788e4df810a92afd627e87958a 100644 (file)
@@ -312,7 +312,7 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
                 <term><option>-s, --sign-type</option></term>
                 <listitem><para>
                     Use particular signature engine. Currently
-                    available <arg choice="plain">ed25519</arg> and <arg choice="plain">dummy</arg>
+                    available <arg choice="plain">ed25519</arg>, <arg choice="plain">spki</arg>, and <arg choice="plain">dummy</arg>
                     signature types.
 
                     The default is <arg choice="plain">ed25519</arg>.
@@ -323,7 +323,8 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
             <varlistentry>
                 <term><option>--sign-from-file</option>="PATH"</term>
                 <listitem><para>
-                        This will read a key (corresponding to the provided <literal>--sign-type</literal> from the provided path.  The key should be base64 encoded.
+                        This will read a key (corresponding to the provided <literal>--sign-type</literal> from the provided path.  The encoding of the key depends on
+                       signature engine. For ed25519 the key should be base64 encoded, for spki it should be in PEM format, and for dummy it should be an ASCII-string.
                 </para></listitem>
             </varlistentry>
 
@@ -337,7 +338,7 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
                         The <literal>KEY-ID</literal> is:
                         <variablelist>
                             <varlistentry>
-                                <term><option>for ed25519:</option></term>
+                                <term><option>for ed25519 and spki:</option></term>
                                 <listitem><para>
                                         <literal>base64</literal>-encoded secret key for commit signing.
                                 </para></listitem>
index 508a6ca22b70c8fe6cb6429fb9e8de6c6329686d..bae07473cbfff1f5525ab41cff01520e5720459f 100644 (file)
@@ -64,26 +64,28 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
         </para>
 
         <para>
-            There are several "well-known" system places for `ed25519` trusted and revoked public keys -- expected single <literal>base64</literal>-encoded key per line.
+            For `ed25519` and `spki`, there are several "well-known" system places for trusted and revoked public keys as listed below.
         </para>
 
         <para>Files:
             <itemizedlist>
-                <listitem><para><filename>/etc/ostree/trusted.ed25519</filename></para></listitem>
-                <listitem><para><filename>/etc/ostree/revoked.ed25519</filename></para></listitem>
-                <listitem><para><filename>/usr/share/ostree/trusted.ed25519</filename></para></listitem>
-                <listitem><para><filename>/usr/share/ostree/revoked.ed25519</filename></para></listitem>
+                <listitem><para><filename>/etc/ostree/trusted.<replaceable>SIGN-TYPE</replaceable></filename></para></listitem>
+                <listitem><para><filename>/etc/ostree/revoked.<replaceable>SIGN-TYPE</replaceable></filename></para></listitem>
+                <listitem><para><filename>/usr/share/ostree/trusted.<replaceable>SIGN-TYPE</replaceable></filename></para></listitem>
+                <listitem><para><filename>/usr/share/ostree/revoked.<replaceable>SIGN-TYPE</replaceable></filename></para></listitem>
             </itemizedlist>
         </para>
 
         <para>Directories containing files with keys:
             <itemizedlist>
-                <listitem><para><filename>/etc/ostree/trusted.ed25519.d</filename></para></listitem>
-                <listitem><para><filename>/etc/ostree/revoked.ed25519.d</filename></para></listitem>
-                <listitem><para><filename>/usr/share/ostree/trusted.ed25519.d</filename></para></listitem>
-                <listitem><para><filename>/usr/share/ostree/revoked.ed25519.d</filename></para></listitem>
+                <listitem><para><filename>/etc/ostree/trusted.<replaceable>SIGN-TYPE</replaceable>.d</filename></para></listitem>
+                <listitem><para><filename>/etc/ostree/revoked.<replaceable>SIGN-TYPE</replaceable>.d</filename></para></listitem>
+                <listitem><para><filename>/usr/share/ostree/trusted.<replaceable>SIGN-TYPE</replaceable>.d</filename></para></listitem>
+                <listitem><para><filename>/usr/share/ostree/revoked.<replaceable>SIGN-TYPE</replaceable>.d</filename></para></listitem>
             </itemizedlist>
         </para>
+
+       <para>The format of those files depends on the signature mechanism; for `ed25519`, keys are stored in the <literal>base64</literal> encoding per line, while for `spki` they are stored in the PEM "PUBLIC KEY" encoding.</para>
     </refsect1>
 
     <refsect1>
@@ -95,7 +97,7 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
                 <listitem><para>
                         <variablelist>
                             <varlistentry>
-                                <term><option>for ed25519:</option></term>
+                                <term><option>for ed25519 and spki:</option></term>
                                 <listitem><para>
                                         <literal>base64</literal>-encoded secret (for signing) or public key (for verifying).
                                 </para></listitem>
@@ -120,7 +122,7 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
                 <term><option>-s, --sign-type</option></term>
                 <listitem><para>
                     Use particular signature mechanism. Currently
-                    available <arg choice="plain">ed25519</arg> and <arg choice="plain">dummy</arg>
+                    available <arg choice="plain">ed25519</arg>, <arg choice="plain">spki</arg>, and <arg choice="plain">dummy</arg>
                     signature types.
 
                     The default is <arg choice="plain">ed25519</arg>.
@@ -133,8 +135,8 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
                 </para></listitem>
 
                 <listitem><para>
-                    Valid for <literal>ed25519</literal> signature type.
-                    For <literal>ed25519</literal> this file must contain <literal>base64</literal>-encoded
+                    Valid for <literal>ed25519</literal> and <literal>spki</literal> signature types.
+                    This file must contain <literal>base64</literal>-encoded
                     secret key(s) (for signing) or public key(s) (for verifying) per line.
                 </para></listitem>
             </varlistentry>
index 4835878808a006e85cb7b65c063cdc58eda1832b..bc5c3533f4b38baefc3a3ca7b60713a3666dd456 100644 (file)
@@ -156,6 +156,7 @@ int main() {
     PRINT_CONSTANT(OSTREE_SHA256_DIGEST_LEN);
     PRINT_CONSTANT(OSTREE_SHA256_STRING_LEN);
     PRINT_CONSTANT(OSTREE_SIGN_NAME_ED25519);
+    PRINT_CONSTANT(OSTREE_SIGN_NAME_SPKI);
     PRINT_CONSTANT((gint) OSTREE_STATIC_DELTA_GENERATE_OPT_LOWLATENCY);
     PRINT_CONSTANT((gint) OSTREE_STATIC_DELTA_GENERATE_OPT_MAJOR);
     PRINT_CONSTANT((gint) OSTREE_STATIC_DELTA_INDEX_FLAGS_NONE);
diff --git a/src/libostree/ostree-sign-spki.c b/src/libostree/ostree-sign-spki.c
new file mode 100644 (file)
index 0000000..5ad81da
--- /dev/null
@@ -0,0 +1,636 @@
+/* vim:set et sw=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e2s: */
+/*
+ * Copyright Â© 2019 Collabora Ltd.
+ * Copyright Â© 2024 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.0+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "ostree-sign-spki.h"
+#include "otcore.h"
+#include <libglnx.h>
+#include <ot-checksum-utils.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "OSTreeSign"
+
+#define OSTREE_SIGN_SPKI_NAME "spki"
+
+typedef enum
+{
+  SPKI_OK,
+  SPKI_NOT_SUPPORTED,
+  SPKI_FAILED_INITIALIZATION
+} spki_state;
+
+struct _OstreeSignSpki
+{
+  GObject parent;
+  spki_state state;
+  GBytes *secret_key;
+  GList *public_keys;  /* GBytes */
+  GList *revoked_keys; /* GBytes */
+};
+
+static void ostree_sign_spki_iface_init (OstreeSignInterface *self);
+
+G_DEFINE_TYPE_WITH_CODE (OstreeSignSpki, _ostree_sign_spki, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (OSTREE_TYPE_SIGN, ostree_sign_spki_iface_init));
+
+static void
+ostree_sign_spki_iface_init (OstreeSignInterface *self)
+{
+
+  self->data = ostree_sign_spki_data;
+  self->data_verify = ostree_sign_spki_data_verify;
+  self->get_name = ostree_sign_spki_get_name;
+  self->metadata_key = ostree_sign_spki_metadata_key;
+  self->metadata_format = ostree_sign_spki_metadata_format;
+  self->clear_keys = ostree_sign_spki_clear_keys;
+  self->set_sk = ostree_sign_spki_set_sk;
+  self->set_pk = ostree_sign_spki_set_pk;
+  self->add_pk = ostree_sign_spki_add_pk;
+  self->load_pk = ostree_sign_spki_load_pk;
+}
+
+static void
+_ostree_sign_spki_class_init (OstreeSignSpkiClass *self)
+{
+}
+
+static void
+_ostree_sign_spki_init (OstreeSignSpki *self)
+{
+
+  self->state = SPKI_OK;
+  self->secret_key = NULL;
+  self->public_keys = NULL;
+  self->revoked_keys = NULL;
+
+#if !defined(USE_OPENSSL)
+  self->state = SPKI_NOT_SUPPORTED;
+#else
+  if (!otcore_spki_init ())
+    self->state = SPKI_FAILED_INITIALIZATION;
+#endif
+}
+
+static gboolean
+_ostree_sign_spki_is_initialized (OstreeSignSpki *self, GError **error)
+{
+  switch (self->state)
+    {
+    case SPKI_OK:
+      break;
+    case SPKI_NOT_SUPPORTED:
+      return glnx_throw (error, "spki: engine is not supported");
+    case SPKI_FAILED_INITIALIZATION:
+      return glnx_throw (error, "spki: crypto library isn't initialized properly");
+    }
+
+  return TRUE;
+}
+
+gboolean
+ostree_sign_spki_data (OstreeSign *self, GBytes *data, GBytes **signature,
+                       GCancellable *cancellable, GError **error)
+{
+#if defined(USE_OPENSSL)
+  g_assert (OSTREE_IS_SIGN (self));
+  OstreeSignSpki *sign = _ostree_sign_spki_get_instance_private (OSTREE_SIGN_SPKI (self));
+
+  if (!_ostree_sign_spki_is_initialized (sign, error))
+    return FALSE;
+
+  if (sign->secret_key == NULL)
+    return glnx_throw (error, "Not able to sign: secret key is not set");
+
+  gsize secret_key_size;
+  const guint8 *secret_key_buf = g_bytes_get_data (sign->secret_key, &secret_key_size);
+
+  EVP_MD_CTX *ctx = EVP_MD_CTX_new ();
+  if (!ctx)
+    return glnx_throw (error, "openssl: failed to allocate context");
+
+  const unsigned char *p = secret_key_buf;
+  EVP_PKEY *pkey = d2i_AutoPrivateKey (NULL, &p, secret_key_size);
+  if (!pkey)
+    {
+      EVP_MD_CTX_free (ctx);
+      return glnx_throw (error, "openssl: Failed to initialize spki key");
+    }
+
+  unsigned long long sig_size = 0;
+  g_autofree guchar *sig = NULL;
+
+  size_t len;
+  if (EVP_DigestSignInit (ctx, NULL, NULL, NULL, pkey)
+      && EVP_DigestSign (ctx, NULL, &len, g_bytes_get_data (data, NULL), g_bytes_get_size (data)))
+    {
+      sig = g_malloc0 (len);
+      if (EVP_DigestSign (ctx, sig, &len, g_bytes_get_data (data, NULL), g_bytes_get_size (data)))
+        sig_size = len;
+    }
+
+  EVP_PKEY_free (pkey);
+  EVP_MD_CTX_free (ctx);
+
+  if (sig_size == 0)
+    return glnx_throw (error, "Failed to sign");
+
+  *signature = g_bytes_new_take (g_steal_pointer (&sig), sig_size);
+  return TRUE;
+#else
+  return glnx_throw (error, "spki signature validation requested, but support not compiled in");
+#endif
+}
+
+gboolean
+ostree_sign_spki_data_verify (OstreeSign *self, GBytes *data, GVariant *signatures,
+                              char **out_success_message, GError **error)
+{
+  g_assert (OSTREE_IS_SIGN (self));
+
+  if (data == NULL)
+    return glnx_throw (error, "spki: unable to verify NULL data");
+
+  OstreeSignSpki *sign = _ostree_sign_spki_get_instance_private (OSTREE_SIGN_SPKI (self));
+
+  if (!_ostree_sign_spki_is_initialized (sign, error))
+    return FALSE;
+
+  if (signatures == NULL)
+    return glnx_throw (error, "spki: commit have no signatures of my type");
+
+  if (!g_variant_is_of_type (signatures, (GVariantType *)OSTREE_SIGN_METADATA_SPKI_TYPE))
+    return glnx_throw (error, "spki: wrong type passed for verification");
+
+  /* If no keys pre-loaded then,
+   * try to load public keys from storage(s) */
+  if (sign->public_keys == NULL)
+    {
+      g_autoptr (GVariantBuilder) builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
+      g_autoptr (GVariant) options = g_variant_builder_end (builder);
+
+      if (!ostree_sign_spki_load_pk (self, options, error))
+        return FALSE;
+    }
+
+  g_debug ("verify: data hash = 0x%x", g_bytes_hash (data));
+
+  g_autoptr (GString) invalid_signatures = NULL;
+  guint n_invalid_signatures = 0;
+
+  for (gsize i = 0; i < g_variant_n_children (signatures); i++)
+    {
+      g_autoptr (GVariant) child = g_variant_get_child_value (signatures, i);
+      g_autoptr (GBytes) signature = g_variant_get_data_as_bytes (child);
+
+      g_debug ("Read signature %d: %s", (gint)i, g_variant_print (child, TRUE));
+
+      for (GList *l = sign->public_keys; l != NULL; l = l->next)
+        {
+          GBytes *public_key = l->data;
+          /* TODO: use non-list for tons of revoked keys? */
+          if (g_list_find_custom (sign->revoked_keys, public_key, g_bytes_compare) != NULL)
+            {
+              g_autofree char *hex = g_malloc0 (g_bytes_get_size (public_key) * 2 + 1);
+              ot_bin2hex (hex, g_bytes_get_data (public_key, NULL), g_bytes_get_size (public_key));
+              g_debug ("Skip revoked key '%s'", hex);
+              continue;
+            }
+
+          bool valid = false;
+          if (!otcore_validate_spki_signature (data, public_key, signature, &valid, error))
+            return FALSE;
+          if (!valid)
+            {
+              /* Incorrect signature! */
+              if (invalid_signatures == NULL)
+                invalid_signatures = g_string_new ("");
+              else
+                g_string_append (invalid_signatures, "; ");
+              n_invalid_signatures++;
+              g_autofree char *hex = g_malloc0 (g_bytes_get_size (public_key) * 2 + 1);
+              ot_bin2hex (hex, g_bytes_get_data (public_key, NULL), g_bytes_get_size (public_key));
+              g_string_append_printf (invalid_signatures, "key '%s'", hex);
+            }
+          else
+            {
+              if (out_success_message)
+                {
+                  g_autofree char *hex = g_malloc0 (g_bytes_get_size (public_key) * 2 + 1);
+                  ot_bin2hex (hex, g_bytes_get_data (public_key, NULL),
+                              g_bytes_get_size (public_key));
+                  *out_success_message = g_strdup_printf (
+                      "spki: Signature verified successfully with key '%s'", hex);
+                }
+              return TRUE;
+            }
+        }
+    }
+
+  if (invalid_signatures)
+    {
+      g_assert_cmpuint (n_invalid_signatures, >, 0);
+      /* The test suite has a key ring with 100 keys.  This seems insane, let's
+       * cap a reasonable error message at 3.
+       */
+      if (n_invalid_signatures > 3)
+        return glnx_throw (error, "spki: Signature couldn't be verified; tried %u keys",
+                           n_invalid_signatures);
+      return glnx_throw (error, "spki: Signature couldn't be verified with: %s",
+                         invalid_signatures->str);
+    }
+  return glnx_throw (error, "spki: no signatures found");
+}
+
+const gchar *
+ostree_sign_spki_get_name (OstreeSign *self)
+{
+  g_assert (OSTREE_IS_SIGN (self));
+
+  return OSTREE_SIGN_SPKI_NAME;
+}
+
+const gchar *
+ostree_sign_spki_metadata_key (OstreeSign *self)
+{
+
+  return OSTREE_SIGN_METADATA_SPKI_KEY;
+}
+
+const gchar *
+ostree_sign_spki_metadata_format (OstreeSign *self)
+{
+
+  return OSTREE_SIGN_METADATA_SPKI_TYPE;
+}
+
+gboolean
+ostree_sign_spki_clear_keys (OstreeSign *self, GError **error)
+{
+  g_assert (OSTREE_IS_SIGN (self));
+
+  OstreeSignSpki *sign = _ostree_sign_spki_get_instance_private (OSTREE_SIGN_SPKI (self));
+
+  if (!_ostree_sign_spki_is_initialized (sign, error))
+    return FALSE;
+
+  /* Clear secret key */
+  if (sign->secret_key != NULL)
+    {
+      gsize size;
+      gpointer data = g_bytes_unref_to_data (sign->secret_key, &size);
+      explicit_bzero (data, size);
+      sign->secret_key = NULL;
+    }
+
+  /* Clear already loaded trusted keys */
+  if (sign->public_keys != NULL)
+    {
+      g_list_free_full (sign->public_keys, (GDestroyNotify)g_bytes_unref);
+      sign->public_keys = NULL;
+    }
+
+  /* Clear already loaded revoked keys */
+  if (sign->revoked_keys != NULL)
+    {
+      g_list_free_full (sign->revoked_keys, (GDestroyNotify)g_bytes_unref);
+      sign->revoked_keys = NULL;
+    }
+
+  return TRUE;
+}
+
+/* Support 2 representations:
+ * base64 ascii -- secret key is passed as string
+ * raw key -- key is passed as bytes array
+ * */
+gboolean
+ostree_sign_spki_set_sk (OstreeSign *self, GVariant *secret_key, GError **error)
+{
+  g_assert (OSTREE_IS_SIGN (self));
+
+  if (!ostree_sign_spki_clear_keys (self, error))
+    return FALSE;
+
+  OstreeSignSpki *sign = _ostree_sign_spki_get_instance_private (OSTREE_SIGN_SPKI (self));
+
+  gsize n_elements = 0;
+
+  g_autofree guchar *secret_key_buf = NULL;
+  if (g_variant_is_of_type (secret_key, G_VARIANT_TYPE_STRING))
+    {
+      const gchar *sk_ascii = g_variant_get_string (secret_key, NULL);
+      secret_key_buf = g_base64_decode (sk_ascii, &n_elements);
+    }
+  else if (g_variant_is_of_type (secret_key, G_VARIANT_TYPE_BYTESTRING))
+    {
+      secret_key_buf
+          = (guchar *)g_variant_get_fixed_array (secret_key, &n_elements, sizeof (guchar));
+    }
+  else
+    {
+      return glnx_throw (error, "Unknown spki secret key type");
+    }
+
+  sign->secret_key = g_bytes_new_take (g_steal_pointer (&secret_key_buf), n_elements);
+
+  return TRUE;
+}
+
+/* Support 2 representations:
+ * base64 ascii -- public key is passed as string
+ * raw key -- key is passed as bytes array
+ * */
+gboolean
+ostree_sign_spki_set_pk (OstreeSign *self, GVariant *public_key, GError **error)
+{
+  g_assert (OSTREE_IS_SIGN (self));
+
+  if (!ostree_sign_spki_clear_keys (self, error))
+    return FALSE;
+
+  return ostree_sign_spki_add_pk (self, public_key, error);
+}
+
+/* Support 2 representations:
+ * base64 ascii -- public key is passed as string
+ * raw key -- key is passed as bytes array
+ * */
+gboolean
+ostree_sign_spki_add_pk (OstreeSign *self, GVariant *public_key, GError **error)
+{
+  g_assert (OSTREE_IS_SIGN (self));
+
+  OstreeSignSpki *sign = _ostree_sign_spki_get_instance_private (OSTREE_SIGN_SPKI (self));
+
+  if (!_ostree_sign_spki_is_initialized (sign, error))
+    return FALSE;
+
+  g_autofree guint8 *key_owned = NULL;
+  const guint8 *key = NULL;
+  gsize n_elements = 0;
+
+  if (g_variant_is_of_type (public_key, G_VARIANT_TYPE_STRING))
+    {
+      const gchar *pk_ascii = g_variant_get_string (public_key, NULL);
+      key = key_owned = g_base64_decode (pk_ascii, &n_elements);
+    }
+  else if (g_variant_is_of_type (public_key, G_VARIANT_TYPE_BYTESTRING))
+    {
+      key = g_variant_get_fixed_array (public_key, &n_elements, sizeof (guchar));
+    }
+  else
+    {
+      return glnx_throw (error, "Unknown spki public key type");
+    }
+
+  g_autofree char *hex = g_malloc0 (n_elements * 2 + 1);
+  ot_bin2hex (hex, key, n_elements);
+  g_debug ("Read spki public key = %s", hex);
+
+  g_autoptr (GBytes) key_bytes = g_bytes_new_static (key, n_elements);
+  if (g_list_find_custom (sign->public_keys, key_bytes, g_bytes_compare) == NULL)
+    {
+      GBytes *new_key_bytes = g_bytes_new (key, n_elements);
+      sign->public_keys = g_list_prepend (sign->public_keys, new_key_bytes);
+    }
+
+  return TRUE;
+}
+
+/* Add revoked public key */
+static gboolean
+_spki_add_revoked (OstreeSign *self, GVariant *revoked_key, GError **error)
+{
+  g_assert (OSTREE_IS_SIGN (self));
+
+  OstreeSignSpki *sign = _ostree_sign_spki_get_instance_private (OSTREE_SIGN_SPKI (self));
+
+  g_autofree guint8 *key_owned = NULL;
+  const guint8 *key = NULL;
+  gsize n_elements = 0;
+
+  if (g_variant_is_of_type (revoked_key, G_VARIANT_TYPE_STRING))
+    {
+      const gchar *rk_ascii = g_variant_get_string (revoked_key, NULL);
+      key = key_owned = g_base64_decode (rk_ascii, &n_elements);
+    }
+  else if (g_variant_is_of_type (revoked_key, G_VARIANT_TYPE_BYTESTRING))
+    {
+      key = g_variant_get_fixed_array (revoked_key, &n_elements, sizeof (guchar));
+    }
+  else
+    {
+      return glnx_throw (error, "Unknown spki revoked key type");
+    }
+
+  g_autofree char *hex = g_malloc0 (n_elements * 2 + 1);
+  ot_bin2hex (hex, key, n_elements);
+  g_debug ("Read spki revoked key = %s", hex);
+
+  g_autoptr (GBytes) key_bytes = g_bytes_new_static (key, n_elements);
+  if (g_list_find_custom (sign->revoked_keys, key, g_bytes_compare) == NULL)
+    {
+      GBytes *new_key_bytes = g_bytes_new (key, n_elements);
+      sign->revoked_keys = g_list_prepend (sign->revoked_keys, new_key_bytes);
+    }
+
+  return TRUE;
+}
+
+static gboolean
+_load_pk_from_stream (OstreeSign *self, GInputStream *key_stream_in, gboolean trusted,
+                      GError **error)
+{
+  if (key_stream_in == NULL)
+    return glnx_throw (error, "spki: unable to read from NULL key-data input stream");
+
+  gboolean ret = FALSE;
+
+  g_autoptr (OstreeBlobReader) blob_reader = ostree_sign_read_pk (self, key_stream_in);
+  g_assert (blob_reader);
+
+  /* Use simple file format with just a list of base64 public keys per line */
+  while (TRUE)
+    {
+      gboolean added = FALSE;
+      g_autoptr (GError) local_error = NULL;
+      g_autoptr (GBytes) blob = ostree_blob_reader_read_blob (blob_reader, NULL, &local_error);
+
+      if (local_error != NULL)
+        {
+          g_propagate_error (error, g_steal_pointer (&local_error));
+          return FALSE;
+        }
+
+      if (blob == NULL)
+        return ret;
+
+      /* Read the key itself */
+      g_autoptr (GVariant) pk = g_variant_new_from_bytes (G_VARIANT_TYPE_BYTESTRING, blob, FALSE);
+
+      if (trusted)
+        added = ostree_sign_spki_add_pk (self, pk, error);
+      else
+        added = _spki_add_revoked (self, pk, error);
+
+      g_autofree gchar *pk_printable = g_variant_print (pk, FALSE);
+      g_debug ("%s %s key: %s", added ? "Added" : "Invalid", trusted ? "public" : "revoked",
+               pk_printable);
+
+      /* Mark what we load at least one key */
+      if (added)
+        ret = TRUE;
+    }
+
+  return ret;
+}
+
+static gboolean
+_load_pk_from_file (OstreeSign *self, const gchar *filename, gboolean trusted, GError **error)
+{
+  g_debug ("Processing file '%s'", filename);
+
+  if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR))
+    {
+      g_debug ("Can't open file '%s' with public keys", filename);
+      return glnx_throw (error, "File object '%s' is not a regular file", filename);
+    }
+
+  g_autoptr (GFile) keyfile = keyfile = g_file_new_for_path (filename);
+  g_autoptr (GFileInputStream) key_stream_in = g_file_read (keyfile, NULL, error);
+  if (key_stream_in == NULL)
+    return FALSE;
+
+  if (!_load_pk_from_stream (self, G_INPUT_STREAM (key_stream_in), trusted, error))
+    {
+      if (error == NULL || *error == NULL)
+        return glnx_throw (error, "signature: spki: no valid keys in file '%s'", filename);
+      else
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+_spki_load_pk (OstreeSign *self, GVariant *options, gboolean trusted, GError **error)
+{
+
+  gboolean ret = FALSE;
+  const gchar *custom_dir = NULL;
+
+  g_autoptr (GPtrArray) base_dirs = g_ptr_array_new_with_free_func (g_free);
+  g_autoptr (GPtrArray) spki_files = g_ptr_array_new_with_free_func (g_free);
+
+  if (g_variant_lookup (options, "basedir", "&s", &custom_dir))
+    {
+      /* Add custom directory */
+      g_ptr_array_add (base_dirs, g_strdup (custom_dir));
+    }
+  else
+    {
+      /* Default paths where to find files with public keys */
+      g_ptr_array_add (base_dirs, g_strdup ("/etc/ostree"));
+      g_ptr_array_add (base_dirs, g_strdup (DATADIR "/ostree"));
+    }
+
+  /* Scan all well-known directories and construct the list with file names to scan keys */
+  for (gint i = 0; i < base_dirs->len; i++)
+    {
+      g_autofree gchar *base_name
+          = g_build_filename ((gchar *)g_ptr_array_index (base_dirs, i),
+                              trusted ? "trusted.spki" : "revoked.spki", NULL);
+
+      g_debug ("Check spki keys from file: %s", base_name);
+      g_autofree gchar *base_dir = g_strconcat (base_name, ".d", NULL);
+      g_ptr_array_add (spki_files, g_steal_pointer (&base_name));
+
+      g_autoptr (GDir) dir = g_dir_open (base_dir, 0, error);
+      if (dir == NULL)
+        {
+          g_clear_error (error);
+          continue;
+        }
+      const gchar *entry = NULL;
+      while ((entry = g_dir_read_name (dir)) != NULL)
+        {
+          gchar *filename = g_build_filename (base_dir, entry, NULL);
+          g_debug ("Check spki keys from file: %s", filename);
+          g_ptr_array_add (spki_files, filename);
+        }
+    }
+
+  /* Scan all well-known files */
+  for (gint i = 0; i < spki_files->len; i++)
+    {
+      if (!_load_pk_from_file (self, (gchar *)g_ptr_array_index (spki_files, i), trusted, error))
+        {
+          g_debug ("Problem with loading spki %s keys from `%s`", trusted ? "public" : "revoked",
+                   (gchar *)g_ptr_array_index (spki_files, i));
+          g_clear_error (error);
+        }
+      else
+        ret = TRUE;
+    }
+
+  if (!ret && (error == NULL || *error == NULL))
+    return glnx_throw (error, "signature: spki: no keys loaded");
+
+  return ret;
+}
+
+/*
+ * options argument should be a{sv}:
+ * - filename -- single file to use to load keys from;
+ * - basedir -- directory containing subdirectories
+ *   'trusted.spki.d' and 'revoked.spki.d' with appropriate
+ *   public keys. Used for testing and re-definition of system-wide
+ *   directories if defaults are not suitable for any reason.
+ */
+gboolean
+ostree_sign_spki_load_pk (OstreeSign *self, GVariant *options, GError **error)
+{
+
+  const gchar *filename = NULL;
+
+  OstreeSignSpki *sign = _ostree_sign_spki_get_instance_private (OSTREE_SIGN_SPKI (self));
+  if (!_ostree_sign_spki_is_initialized (sign, error))
+    return FALSE;
+
+  /* Read keys only from single file provided */
+  if (g_variant_lookup (options, "filename", "&s", &filename))
+    return _load_pk_from_file (self, filename, TRUE, error);
+
+  /* Load public keys from well-known directories and files */
+  if (!_spki_load_pk (self, options, TRUE, error))
+    return FALSE;
+
+  /* Load untrusted keys from well-known directories and files
+   * Ignore the failure from this function -- it is expected to have
+   * empty list of revoked keys.
+   * */
+  if (!_spki_load_pk (self, options, FALSE, error))
+    g_clear_error (error);
+
+  return TRUE;
+}
diff --git a/src/libostree/ostree-sign-spki.h b/src/libostree/ostree-sign-spki.h
new file mode 100644 (file)
index 0000000..089bd27
--- /dev/null
@@ -0,0 +1,54 @@
+/* vim:set et sw=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e2s: */
+
+/*
+ * Copyright Â© 2019 Collabora Ltd.
+ * Copyright Â© 2024 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.0+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ostree-sign.h"
+
+G_BEGIN_DECLS
+
+#define OSTREE_TYPE_SIGN_SPKI (_ostree_sign_spki_get_type ())
+
+_OSTREE_PUBLIC
+G_DECLARE_FINAL_TYPE (OstreeSignSpki, _ostree_sign_spki, OSTREE, SIGN_SPKI, GObject)
+
+gboolean ostree_sign_spki_data (OstreeSign *self, GBytes *data, GBytes **signature,
+                                GCancellable *cancellable, GError **error);
+
+gboolean ostree_sign_spki_data_verify (OstreeSign *self, GBytes *data, GVariant *signatures,
+                                       char **out_success_message, GError **error);
+
+const gchar *ostree_sign_spki_get_name (OstreeSign *self);
+const gchar *ostree_sign_spki_metadata_key (OstreeSign *self);
+const gchar *ostree_sign_spki_metadata_format (OstreeSign *self);
+
+gboolean ostree_sign_spki_clear_keys (OstreeSign *self, GError **error);
+
+gboolean ostree_sign_spki_set_sk (OstreeSign *self, GVariant *secret_key, GError **error);
+
+gboolean ostree_sign_spki_set_pk (OstreeSign *self, GVariant *public_key, GError **error);
+
+gboolean ostree_sign_spki_add_pk (OstreeSign *self, GVariant *public_key, GError **error);
+
+gboolean ostree_sign_spki_load_pk (OstreeSign *self, GVariant *options, GError **error);
+
+G_END_DECLS
index a086efa872f3206908fb28d5c525ef7fd6d3972c..97699f204bf6f0ea2af99e3184e930cd019e465a 100644 (file)
 
 #include "ostree-autocleanups.h"
 #include "ostree-blob-reader-base64.h"
+#include "ostree-blob-reader-pem.h"
 #include "ostree-blob-reader-raw.h"
 #include "ostree-core.h"
 #include "ostree-sign-dummy.h"
 #include "ostree-sign-ed25519.h"
 #include "ostree-sign-private.h"
+#include "ostree-sign-spki.h"
 #include "ostree-sign.h"
 
 #include "ostree-autocleanups.h"
@@ -61,6 +63,9 @@ typedef struct
 _sign_type sign_types[] = {
 #if defined(HAVE_ED25519)
   { OSTREE_SIGN_NAME_ED25519, 0 },
+#endif
+#if defined(HAVE_SPKI)
+  { OSTREE_SIGN_NAME_SPKI, 0 },
 #endif
   { "dummy", 0 }
 };
@@ -69,6 +74,9 @@ enum
 {
 #if defined(HAVE_ED25519)
   SIGN_ED25519,
+#endif
+#if defined(HAVE_SPKI)
+  SIGN_SPKI,
 #endif
   SIGN_DUMMY
 };
@@ -538,6 +546,10 @@ ostree_sign_get_by_name (const gchar *name, GError **error)
 #if defined(HAVE_ED25519)
   if (sign_types[SIGN_ED25519].type == 0)
     sign_types[SIGN_ED25519].type = OSTREE_TYPE_SIGN_ED25519;
+#endif
+#if defined(HAVE_SPKI)
+  if (sign_types[SIGN_SPKI].type == 0)
+    sign_types[SIGN_SPKI].type = OSTREE_TYPE_SIGN_SPKI;
 #endif
   if (sign_types[SIGN_DUMMY].type == 0)
     sign_types[SIGN_DUMMY].type = OSTREE_TYPE_SIGN_DUMMY;
@@ -661,6 +673,10 @@ ostree_sign_read_pk (OstreeSign *self, GInputStream *stream)
 #if defined(HAVE_ED25519)
   if (OSTREE_IS_SIGN_ED25519 (self))
     return OSTREE_BLOB_READER (_ostree_blob_reader_base64_new (stream));
+#endif
+#if defined(HAVE_SPKI)
+  if (OSTREE_IS_SIGN_SPKI (self))
+    return OSTREE_BLOB_READER (_ostree_blob_reader_pem_new (stream, "PUBLIC KEY"));
 #endif
   if (OSTREE_IS_SIGN_DUMMY (self))
     return OSTREE_BLOB_READER (_ostree_blob_reader_raw_new (stream));
@@ -684,6 +700,10 @@ ostree_sign_read_sk (OstreeSign *self, GInputStream *stream)
 #if defined(HAVE_ED25519)
   if (OSTREE_IS_SIGN_ED25519 (self))
     return OSTREE_BLOB_READER (_ostree_blob_reader_base64_new (stream));
+#endif
+#if defined(HAVE_SPKI)
+  if (OSTREE_IS_SIGN_SPKI (self))
+    return OSTREE_BLOB_READER (_ostree_blob_reader_pem_new (stream, "PRIVATE KEY"));
 #endif
   if (OSTREE_IS_SIGN_DUMMY (self))
     return OSTREE_BLOB_READER (_ostree_blob_reader_raw_new (stream));
index 81495c3900f16579cb8a0a598386f3088cdc3885..a817f32ada8342a29fd6b09f4a43608eaba383e5 100644 (file)
@@ -44,6 +44,14 @@ G_BEGIN_DECLS
  */
 #define OSTREE_SIGN_NAME_ED25519 "ed25519"
 
+/**
+ * OSTREE_SIGN_NAME_SPKI:
+ * The name of the spki signing type.
+ *
+ * Since: 2024.7
+ */
+#define OSTREE_SIGN_NAME_SPKI "spki"
+
 _OSTREE_PUBLIC
 G_DECLARE_INTERFACE (OstreeSign, ostree_sign, OSTREE, SIGN, GObject)
 
diff --git a/src/libotcore/otcore-spki-verify.c b/src/libotcore/otcore-spki-verify.c
new file mode 100644 (file)
index 0000000..3cf22f6
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.0+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "otcore.h"
+
+/* Initialize global state; may be called multiple times and is idempotent. */
+bool
+otcore_spki_init (void)
+{
+  return true;
+}
+
+/* Validate a single spki signature.  If there is an unexpected state, such
+ * as an ill-forumed public key or signature, a hard error will be returned.
+ *
+ * If the signature is not correct, this function will return successfully, but
+ * `out_valid` will be set to `false`.
+ *
+ * If the signature is correct, `out_valid` will be `true`.
+ */
+gboolean
+otcore_validate_spki_signature (GBytes *data, GBytes *public_key, GBytes *signature,
+                                bool *out_valid, GError **error)
+{
+  // Since this is signature verification code, let's verify preconditions.
+  g_assert (data);
+  g_assert (public_key);
+  g_assert (signature);
+  g_assert (out_valid);
+  // It is OK for error to be NULL, though according to GError rules.
+
+#if defined(HAVE_OPENSSL)
+  gsize public_key_size;
+  const guint8 *public_key_buf = g_bytes_get_data (public_key, &public_key_size);
+
+  gsize signature_size;
+  const guint8 *signature_buf = g_bytes_get_data (signature, &signature_size);
+
+  if (public_key_size > OSTREE_SIGN_MAX_METADATA_SIZE)
+    return glnx_throw (
+        error, "Invalid public key of %" G_GSIZE_FORMAT " bytes, expected <= %" G_GSIZE_FORMAT,
+        public_key_size, (gsize)OSTREE_SIGN_MAX_METADATA_SIZE);
+
+  if (signature_size > OSTREE_SIGN_MAX_METADATA_SIZE)
+    return glnx_throw (
+        error, "Invalid signature of %" G_GSIZE_FORMAT " bytes, expected <= %" G_GSIZE_FORMAT,
+        signature_size, (gsize)OSTREE_SIGN_MAX_METADATA_SIZE);
+
+  EVP_MD_CTX *ctx = EVP_MD_CTX_new ();
+  if (!ctx)
+    return glnx_throw (error, "openssl: failed to allocate context");
+
+  const unsigned char *p = public_key_buf;
+  EVP_PKEY *pkey = d2i_PUBKEY (NULL, &p, public_key_size);
+  if (!pkey)
+    {
+      EVP_MD_CTX_free (ctx);
+      return glnx_throw (error, "openssl: Failed to initialize spki key");
+    }
+  if (EVP_DigestVerifyInit (ctx, NULL, NULL, NULL, pkey) != 0
+      && EVP_DigestVerify (ctx, signature_buf, signature_size, g_bytes_get_data (data, NULL),
+                           g_bytes_get_size (data))
+             != 0)
+    {
+      *out_valid = true;
+    }
+  EVP_PKEY_free (pkey);
+  EVP_MD_CTX_free (ctx);
+  return TRUE;
+#else
+  return glnx_throw (error, "spki signature validation requested, but support not compiled in");
+#endif
+}
index 1213880f8f12d74f67e79858d24e2a305c92b707..ceeb1a9265501d27c2fbcef254b52575b980626f 100644 (file)
@@ -27,6 +27,7 @@
 #define USE_LIBSODIUM
 #elif defined(HAVE_OPENSSL)
 #include <openssl/evp.h>
+#include <openssl/x509.h>
 #define USE_OPENSSL
 #endif
 
 // The variant type
 #define OSTREE_SIGN_METADATA_ED25519_TYPE "aay"
 
+// This key is stored inside commit metadata.
+#define OSTREE_SIGN_METADATA_SPKI_KEY "ostree.sign.spki"
+// The variant type
+#define OSTREE_SIGN_METADATA_SPKI_TYPE "aay"
+
+// Maximum size of metadata in bytes, in sync with OSTREE_MAX_METADATA_SIZE
+#define OSTREE_SIGN_MAX_METADATA_SIZE (128 * 1024 * 1024)
+
 bool otcore_ed25519_init (void);
 gboolean otcore_validate_ed25519_signature (GBytes *data, GBytes *pubkey, GBytes *signature,
                                             bool *out_valid, GError **error);
 
+bool otcore_spki_init (void);
+gboolean otcore_validate_spki_signature (GBytes *data, GBytes *public_key, GBytes *signature,
+                                         bool *out_valid, GError **error);
+
 char *otcore_find_proc_cmdline_key (const char *cmdline, const char *key);
 gboolean otcore_get_ostree_target (const char *cmdline, gboolean *is_aboot, char **out_target,
                                    GError **error);
index 2c2a33f0d947d1d918884c14cf0fa86cc7242f81..f8a7dc74e4ce15358bdbdf7a59c3e3718326dcf7 100755 (executable)
@@ -780,6 +780,40 @@ gen_ed25519_random_public()
   openssl genpkey -algorithm ED25519 | openssl pkey -outform DER | tail -c 32 | base64
 }
 
+# Keys for spki signing tests
+SPKIPUBLICPEM=
+SPKISECRETPEM=
+
+SPKIPUBLIC=
+SPKISECRET=
+
+gen_spki_keys ()
+{
+  # Generate private key in PEM format
+  SPKISECRETPEM="$(mktemp -p ${test_tmpdir} ed448_sk_XXXXXX.pem)"
+  openssl genpkey -algorithm ed448 -outform PEM -out "${SPKISECRETPEM}"
+  SPKIPUBLICPEM="$(mktemp -p ${test_tmpdir} ed448_pk_XXXXXX.pem)"
+  openssl pkey -outform PEM -pubout -in "${SPKISECRETPEM}" -out "${SPKIPUBLICPEM}"
+
+  SPKIPUBLIC="$(openssl pkey -inform PEM -outform DER -pubin -pubout -in ${SPKIPUBLICPEM} | base64 -w 0)"
+  SPKISECRET="$(openssl pkey -inform PEM -outform DER -in ${SPKISECRETPEM} | base64 -w 0)"
+
+  echo "Generated ed448 keys:"
+  echo "public: ${SPKIPUBLIC}"
+  echo "secret: ${SPKISECRET}"
+}
+
+gen_spki_random_public()
+{
+    openssl genpkey -algorithm ed448 | openssl pkey -pubout -outform DER | base64 -w 0
+    echo
+}
+
+gen_spki_random_public_pem()
+{
+  openssl genpkey -algorithm ed448 | openssl pkey -pubout -outform PEM
+}
+
 is_bare_user_only_repo () {
   grep -q 'mode=bare-user-only' $1/config
 }
diff --git a/tests/test-signed-commit-dummy.sh b/tests/test-signed-commit-dummy.sh
new file mode 100755 (executable)
index 0000000..305af38
--- /dev/null
@@ -0,0 +1,61 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 Collabora Ltd.
+#
+# SPDX-License-Identifier: LGPL-2.0+
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <https://www.gnu.org/licenses/>.
+
+set -euo pipefail
+
+. $(dirname $0)/libtest.sh
+
+# This is explicitly opt in for testing
+export OSTREE_DUMMY_SIGN_ENABLED=1
+
+mkdir ${test_tmpdir}/repo
+ostree_repo_init repo --mode="archive"
+
+echo "Unsigned commit" > file.txt
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s 'Unsigned commit'
+COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
+
+# Test `ostree sign` with dummy module first
+DUMMYSIGN="dummysign"
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy ${COMMIT} ${DUMMYSIGN}
+
+# Ensure that detached metadata really contain expected string
+EXPECTEDSIGN="$(echo $DUMMYSIGN | hexdump -n 9 -e '8/1 "0x%.2x, " 1/1 " 0x%.2x"')"
+${CMD_PREFIX} ostree --repo=repo show ${COMMIT} --print-detached-metadata-key=ostree.sign.dummy | grep -q -e "${EXPECTEDSIGN}"
+tap_ok "Detached dummy signature added"
+
+# Verify vith sign mechanism
+${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --sign-type=dummy --verify ${COMMIT} ${DUMMYSIGN}
+tap_ok "dummy signature verified"
+
+echo "Signed commit with dummy key: ${DUMMYSIGN}" >> file.txt
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s 'Signed with dummy module' --sign=${DUMMYSIGN} --sign-type=dummy 
+COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy --verify ${COMMIT} ${DUMMYSIGN}
+tap_ok "commit with dummy signing"
+
+if ${CMD_PREFIX} env -u OSTREE_DUMMY_SIGN_ENABLED ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy --verify ${COMMIT} ${DUMMYSIGN} 2>err.txt; then
+    fatal "verified dummy signature without env"
+fi
+# FIXME the error message here is broken
+#assert_file_has_content_literal err.txt 'dummy signature type is only for ostree testing'
+assert_file_has_content_literal err.txt ' No valid signatures found'
+tap_ok "dummy sig requires env"
+
+tap_end
diff --git a/tests/test-signed-commit-ed25519.sh b/tests/test-signed-commit-ed25519.sh
new file mode 100755 (executable)
index 0000000..5408ca9
--- /dev/null
@@ -0,0 +1,164 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 Collabora Ltd.
+#
+# SPDX-License-Identifier: LGPL-2.0+
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <https://www.gnu.org/licenses/>.
+
+set -euo pipefail
+
+. $(dirname $0)/libtest.sh
+
+# This is explicitly opt in for testing
+export OSTREE_DUMMY_SIGN_ENABLED=1
+
+mkdir ${test_tmpdir}/repo
+ostree_repo_init repo --mode="archive"
+
+# For multi-sign test
+DUMMYSIGN="dummysign"
+
+# Test ostree sign with 'ed25519' module
+gen_ed25519_keys
+PUBLIC=${ED25519PUBLIC}
+SECRET=${ED25519SECRET}
+
+WRONG_PUBLIC="$(gen_ed25519_random_public)"
+
+echo "PUBLIC = $PUBLIC"
+
+echo "Signed commit with ed25519: ${SECRET}" >> file.txt
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s "Signed with ed25519 module" --sign="${SECRET}" --sign-type=ed25519
+COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
+
+# Ensure that detached metadata contain signature
+${CMD_PREFIX} ostree --repo=repo show ${COMMIT} --print-detached-metadata-key=ostree.sign.ed25519 &>/dev/null
+tap_ok "Detached ed25519 signature added"
+
+# Verify vith sign mechanism
+if ${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} ${WRONG_PUBLIC}; then
+    exit 1
+fi
+${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} ${PUBLIC}
+${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} ${PUBLIC} ${PUBLIC}
+${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} $(gen_ed25519_random_public) ${PUBLIC}
+${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} $(gen_ed25519_random_public) $(gen_ed25519_random_public) ${PUBLIC}
+${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} ${PUBLIC} $(gen_ed25519_random_public) $(gen_ed25519_random_public)
+${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} $(gen_ed25519_random_public) $(gen_ed25519_random_public) ${PUBLIC} $(gen_ed25519_random_public) $(gen_ed25519_random_public)
+tap_ok "ed25519 signature verified"
+
+# Check if we are able to use all available modules to sign the same commit
+echo "Unsigned commit for multi-sign" >> file.txt
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s 'Unsigned commit'
+COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
+# Check if we have no signatures
+for mod in "dummy" "ed25519"; do
+    if ostree --repo=repo show ${COMMIT} --print-detached-metadata-key=ostree.sign.${mod}; then
+        echo "Unexpected signature for ${mod} found"
+        exit 1
+    fi
+done
+
+# Sign with all available modules
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy ${COMMIT} ${DUMMYSIGN}
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=ed25519 ${COMMIT} ${SECRET}
+# and verify
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} ${PUBLIC} >out.txt
+assert_file_has_content out.txt "ed25519: Signature verified successfully with key"
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy --verify ${COMMIT} ${DUMMYSIGN} >out.txt
+assert_file_has_content out.txt "dummy: Signature verified"
+tap_ok "multiple signing "
+
+# Prepare files with public ed25519 signatures
+PUBKEYS="$(mktemp -p ${test_tmpdir} ed25519_XXXXXX.ed25519)"
+
+# Test if file contain no keys
+if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${PUBKEYS} ${COMMIT}; then
+    exit 1
+fi
+
+# Test if have a problem with file object
+if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${test_tmpdir} ${COMMIT}; then
+    exit 1
+fi
+
+# Test with single key in list
+echo ${PUBLIC} > ${PUBKEYS}
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${PUBKEYS} ${COMMIT} >out.txt
+assert_file_has_content out.txt 'ed25519: Signature verified successfully'
+
+# Test the file with multiple keys without a valid public key
+for((i=0;i<100;i++)); do
+    # Generate a list with some public signatures
+    gen_ed25519_random_public
+done > ${PUBKEYS}
+# Check if file contain no valid signatures
+if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${PUBKEYS} ${COMMIT} 2>err.txt; then
+    fatal "validated with no signatures"
+fi
+assert_file_has_content err.txt 'error:.* ed25519: Signature couldn.t be verified; tried 100 keys'
+# Check if no valid signatures provided via args&file
+if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${PUBKEYS} ${COMMIT} ${WRONG_PUBLIC}; then
+    exit 1
+fi
+
+#Test keys file and public key
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${PUBKEYS} ${COMMIT} ${PUBLIC}
+
+# Add correct key into the list
+echo ${PUBLIC} >> ${PUBKEYS}
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${PUBKEYS} ${COMMIT}
+
+tap_ok "verify ed25519 keys file"
+
+# Check ed25519 signing with secret file
+echo "Unsigned commit for secret file usage" >> file.txt
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s 'Unsigned commit'
+COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
+
+KEYFILE="$(mktemp -p ${test_tmpdir} secret_XXXXXX.ed25519)"
+echo "${SECRET}" > ${KEYFILE}
+# Sign
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=ed25519 --keys-file=${KEYFILE} ${COMMIT}
+# Verify
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${PUBKEYS} ${COMMIT}
+tap_ok "sign with ed25519 keys file"
+
+# Check the well-known places mechanism
+mkdir -p ${test_tmpdir}/{trusted,revoked}.ed25519.d
+for((i=0;i<100;i++)); do
+    # Generate some key files with random public signatures
+    gen_ed25519_random_public > ${test_tmpdir}/trusted.ed25519.d/signature_$i
+done
+# Check no valid public keys are available
+if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-dir=${test_tmpdir} ${COMMIT}; then
+    exit 1
+fi
+echo ${PUBLIC} > ${test_tmpdir}/trusted.ed25519.d/correct
+# Verify with correct key
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-dir=${test_tmpdir} ${COMMIT}
+
+tap_ok "verify ed25519 system-wide configuration"
+
+# Add the public key into revoked list
+echo ${PUBLIC} > ${test_tmpdir}/revoked.ed25519.d/correct
+# Check if public key is not valid anymore
+if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-dir=${test_tmpdir} ${COMMIT}; then
+    exit 1
+fi
+rm -rf ${test_tmpdir}/{trusted,revoked}.ed25519.d
+tap_ok "verify ed25519 revoking keys mechanism"
+
+tap_end
diff --git a/tests/test-signed-commit-spki.sh b/tests/test-signed-commit-spki.sh
new file mode 100755 (executable)
index 0000000..68b84f1
--- /dev/null
@@ -0,0 +1,164 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 Collabora Ltd.
+#
+# SPDX-License-Identifier: LGPL-2.0+
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <https://www.gnu.org/licenses/>.
+
+set -euo pipefail
+
+. $(dirname $0)/libtest.sh
+
+# This is explicitly opt in for testing
+export OSTREE_DUMMY_SIGN_ENABLED=1
+
+mkdir ${test_tmpdir}/repo
+ostree_repo_init repo --mode="archive"
+
+# For multi-sign test
+DUMMYSIGN="dummysign"
+
+# Test ostree sign with 'spki' module
+gen_spki_keys
+PUBLIC=${SPKIPUBLIC}
+SECRET=${SPKISECRET}
+
+WRONG_PUBLIC="$(gen_spki_random_public)"
+
+echo "PUBLIC = $PUBLIC"
+
+echo "Signed commit with spki: ${SECRET}" >> file.txt
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s "Signed with spki module" --sign="${SECRET}" --sign-type=spki
+COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
+
+# Ensure that detached metadata contain signature
+${CMD_PREFIX} ostree --repo=repo show ${COMMIT} --print-detached-metadata-key=ostree.sign.spki &>/dev/null
+tap_ok "Detached spki signature added"
+
+# Verify vith sign mechanism
+if ${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=spki ${COMMIT} ${WRONG_PUBLIC}; then
+    exit 1
+fi
+${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=spki ${COMMIT} ${PUBLIC}
+${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=spki ${COMMIT} ${PUBLIC} ${PUBLIC}
+${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=spki ${COMMIT} $(gen_spki_random_public) ${PUBLIC}
+${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=spki ${COMMIT} $(gen_spki_random_public) $(gen_spki_random_public) ${PUBLIC}
+${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=spki ${COMMIT} ${PUBLIC} $(gen_spki_random_public) $(gen_spki_random_public)
+${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=spki ${COMMIT} $(gen_spki_random_public) $(gen_spki_random_public) ${PUBLIC} $(gen_spki_random_public) $(gen_spki_random_public)
+tap_ok "spki signature verified"
+
+# Check if we are able to use all available modules to sign the same commit
+echo "Unsigned commit for multi-sign" >> file.txt
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s 'Unsigned commit'
+COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
+# Check if we have no signatures
+for mod in "dummy" "spki"; do
+    if ostree --repo=repo show ${COMMIT} --print-detached-metadata-key=ostree.sign.${mod}; then
+        echo "Unexpected signature for ${mod} found"
+        exit 1
+    fi
+done
+
+# Sign with all available modules
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy ${COMMIT} ${DUMMYSIGN}
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=spki ${COMMIT} ${SECRET}
+# and verify
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki ${COMMIT} ${PUBLIC} >out.txt
+assert_file_has_content out.txt "spki: Signature verified successfully with key"
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy --verify ${COMMIT} ${DUMMYSIGN} >out.txt
+assert_file_has_content out.txt "dummy: Signature verified"
+tap_ok "multiple signing "
+
+# Prepare files with public spki signatures
+PUBKEYS="$(mktemp -p ${test_tmpdir} spki_XXXXXX.spki)"
+
+# Test if file contain no keys
+if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-file=${PUBKEYS} ${COMMIT}; then
+    exit 1
+fi
+
+# Test if have a problem with file object
+if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-file=${test_tmpdir} ${COMMIT}; then
+    exit 1
+fi
+
+# Test with single key in list
+cat ${SPKIPUBLICPEM} > ${PUBKEYS}
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-file=${PUBKEYS} ${COMMIT} >out.txt
+assert_file_has_content out.txt 'spki: Signature verified successfully'
+
+# Test the file with multiple keys without a valid public key
+for((i=0;i<100;i++)); do
+    # Generate a list with some public signatures
+    gen_spki_random_public_pem
+done > ${PUBKEYS}
+# Check if file contain no valid signatures
+if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-file=${PUBKEYS} ${COMMIT} 2>err.txt; then
+    fatal "validated with no signatures"
+fi
+assert_file_has_content err.txt 'error:.* spki: Signature couldn.t be verified; tried 100 keys'
+# Check if no valid signatures provided via args&file
+if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-file=${PUBKEYS} ${COMMIT} ${WRONG_PUBLIC}; then
+    exit 1
+fi
+
+#Test keys file and public key
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-file=${PUBKEYS} ${COMMIT} ${PUBLIC}
+
+# Add correct key into the list
+cat "${SPKIPUBLICPEM}" >> ${PUBKEYS}
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-file=${PUBKEYS} ${COMMIT}
+
+tap_ok "verify spki keys file"
+
+# Check spki signing with secret file
+echo "Unsigned commit for secret file usage" >> file.txt
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s 'Unsigned commit'
+COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
+
+KEYFILE="$(mktemp -p ${test_tmpdir} secret_XXXXXX.spki)"
+cat "${SPKISECRETPEM}" > ${KEYFILE}
+# Sign
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=spki --keys-file=${KEYFILE} ${COMMIT}
+# Verify
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-file=${PUBKEYS} ${COMMIT}
+tap_ok "sign with spki keys file"
+
+# Check the well-known places mechanism
+mkdir -p ${test_tmpdir}/{trusted,revoked}.spki.d
+for((i=0;i<100;i++)); do
+    # Generate some key files with random public signatures
+    gen_spki_random_public_pem > ${test_tmpdir}/trusted.spki.d/signature_$i
+done
+# Check no valid public keys are available
+if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-dir=${test_tmpdir} ${COMMIT}; then
+    exit 1
+fi
+cat "${SPKIPUBLICPEM}" > ${test_tmpdir}/trusted.spki.d/correct
+# Verify with correct key
+${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-dir=${test_tmpdir} ${COMMIT}
+
+tap_ok "verify spki system-wide configuration"
+
+# Add the public key into revoked list
+cat "${SPKIPUBLICPEM}" > ${test_tmpdir}/revoked.spki.d/correct
+# Check if public key is not valid anymore
+if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=spki --keys-dir=${test_tmpdir} ${COMMIT}; then
+    exit 1
+fi
+rm -rf ${test_tmpdir}/{trusted,revoked}.spki.d
+tap_ok "verify spki revoking keys mechanism"
+
+tap_end
diff --git a/tests/test-signed-commit.sh b/tests/test-signed-commit.sh
deleted file mode 100755 (executable)
index 3fac605..0000000
+++ /dev/null
@@ -1,204 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2019 Collabora Ltd.
-#
-# SPDX-License-Identifier: LGPL-2.0+
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library. If not, see <https://www.gnu.org/licenses/>.
-
-set -euo pipefail
-
-. $(dirname $0)/libtest.sh
-
-# This is explicitly opt in for testing
-export OSTREE_DUMMY_SIGN_ENABLED=1
-
-mkdir ${test_tmpdir}/repo
-ostree_repo_init repo --mode="archive"
-
-echo "Unsigned commit" > file.txt
-${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s 'Unsigned commit'
-COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
-
-# Test `ostree sign` with dummy module first
-DUMMYSIGN="dummysign"
-${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy ${COMMIT} ${DUMMYSIGN}
-
-# Ensure that detached metadata really contain expected string
-EXPECTEDSIGN="$(echo $DUMMYSIGN | hexdump -n 9 -e '8/1 "0x%.2x, " 1/1 " 0x%.2x"')"
-${CMD_PREFIX} ostree --repo=repo show ${COMMIT} --print-detached-metadata-key=ostree.sign.dummy | grep -q -e "${EXPECTEDSIGN}"
-tap_ok "Detached dummy signature added"
-
-# Verify vith sign mechanism
-${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --sign-type=dummy --verify ${COMMIT} ${DUMMYSIGN}
-tap_ok "dummy signature verified"
-
-echo "Signed commit with dummy key: ${DUMMYSIGN}" >> file.txt
-${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s 'Signed with dummy module' --sign=${DUMMYSIGN} --sign-type=dummy 
-COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
-${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy --verify ${COMMIT} ${DUMMYSIGN}
-tap_ok "commit with dummy signing"
-
-if ${CMD_PREFIX} env -u OSTREE_DUMMY_SIGN_ENABLED ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy --verify ${COMMIT} ${DUMMYSIGN} 2>err.txt; then
-    fatal "verified dummy signature without env"
-fi
-# FIXME the error message here is broken
-#assert_file_has_content_literal err.txt 'dummy signature type is only for ostree testing'
-assert_file_has_content_literal err.txt ' No valid signatures found'
-tap_ok "dummy sig requires env"
-
-# tests below require libsodium support
-if ! has_ostree_feature sign-ed25519; then
-    tap_ok "Detached ed25519 signature # SKIP due libsodium unavailability"
-    tap_ok "ed25519 signature verified # SKIP due libsodium unavailability"
-    tap_ok "multiple signing # SKIP due libsodium unavailability"
-    tap_ok "verify ed25519 keys file # SKIP due libsodium unavailability"
-    tap_ok "sign with ed25519 keys file # SKIP due libsodium unavailability"
-    tap_ok "verify ed25519 system-wide configuration # SKIP due libsodium unavailability"
-    tap_ok "verify ed25519 revoking keys mechanism # SKIP due libsodium unavailability"
-    exit 0
-fi
-
-# Test ostree sign with 'ed25519' module
-gen_ed25519_keys
-PUBLIC=${ED25519PUBLIC}
-SECRET=${ED25519SECRET}
-
-WRONG_PUBLIC="$(gen_ed25519_random_public)"
-
-echo "PUBLIC = $PUBLIC"
-
-echo "Signed commit with ed25519: ${SECRET}" >> file.txt
-${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s "Signed with ed25519 module" --sign="${SECRET}" --sign-type=ed25519
-COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
-
-# Ensure that detached metadata contain signature
-${CMD_PREFIX} ostree --repo=repo show ${COMMIT} --print-detached-metadata-key=ostree.sign.ed25519 &>/dev/null
-tap_ok "Detached ed25519 signature added"
-
-# Verify vith sign mechanism
-if ${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} ${WRONG_PUBLIC}; then
-    exit 1
-fi
-${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} ${PUBLIC}
-${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} ${PUBLIC} ${PUBLIC}
-${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} $(gen_ed25519_random_public) ${PUBLIC}
-${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} $(gen_ed25519_random_public) $(gen_ed25519_random_public) ${PUBLIC}
-${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} ${PUBLIC} $(gen_ed25519_random_public) $(gen_ed25519_random_public)
-${CMD_PREFIX} ostree  --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} $(gen_ed25519_random_public) $(gen_ed25519_random_public) ${PUBLIC} $(gen_ed25519_random_public) $(gen_ed25519_random_public)
-tap_ok "ed25519 signature verified"
-
-# Check if we are able to use all available modules to sign the same commit
-echo "Unsigned commit for multi-sign" >> file.txt
-${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s 'Unsigned commit'
-COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
-# Check if we have no signatures
-for mod in "dummy" "ed25519"; do
-    if ostree --repo=repo show ${COMMIT} --print-detached-metadata-key=ostree.sign.${mod}; then
-        echo "Unexpected signature for ${mod} found"
-        exit 1
-    fi
-done
-
-# Sign with all available modules
-${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy ${COMMIT} ${DUMMYSIGN}
-${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=ed25519 ${COMMIT} ${SECRET}
-# and verify
-${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 ${COMMIT} ${PUBLIC} >out.txt
-assert_file_has_content out.txt "ed25519: Signature verified successfully with key"
-${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=dummy --verify ${COMMIT} ${DUMMYSIGN} >out.txt
-assert_file_has_content out.txt "dummy: Signature verified"
-tap_ok "multiple signing "
-
-# Prepare files with public ed25519 signatures
-PUBKEYS="$(mktemp -p ${test_tmpdir} ed25519_XXXXXX.ed25519)"
-
-# Test if file contain no keys
-if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${PUBKEYS} ${COMMIT}; then
-    exit 1
-fi
-
-# Test if have a problem with file object
-if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${test_tmpdir} ${COMMIT}; then
-    exit 1
-fi
-
-# Test with single key in list
-echo ${PUBLIC} > ${PUBKEYS}
-${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${PUBKEYS} ${COMMIT} >out.txt
-assert_file_has_content out.txt 'ed25519: Signature verified successfully'
-
-# Test the file with multiple keys without a valid public key
-for((i=0;i<100;i++)); do
-    # Generate a list with some public signatures
-    gen_ed25519_random_public
-done > ${PUBKEYS}
-# Check if file contain no valid signatures
-if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${PUBKEYS} ${COMMIT} 2>err.txt; then
-    fatal "validated with no signatures"
-fi
-assert_file_has_content err.txt 'error:.* ed25519: Signature couldn.t be verified; tried 100 keys'
-# Check if no valid signatures provided via args&file
-if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${PUBKEYS} ${COMMIT} ${WRONG_PUBLIC}; then
-    exit 1
-fi
-
-#Test keys file and public key
-${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${PUBKEYS} ${COMMIT} ${PUBLIC}
-
-# Add correct key into the list
-echo ${PUBLIC} >> ${PUBKEYS}
-${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${PUBKEYS} ${COMMIT}
-
-tap_ok "verify ed25519 keys file"
-
-# Check ed25519 signing with secret file
-echo "Unsigned commit for secret file usage" >> file.txt
-${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo commit -b main -s 'Unsigned commit'
-COMMIT="$(ostree --repo=${test_tmpdir}/repo rev-parse main)"
-
-KEYFILE="$(mktemp -p ${test_tmpdir} secret_XXXXXX.ed25519)"
-echo "${SECRET}" > ${KEYFILE}
-# Sign
-${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --sign-type=ed25519 --keys-file=${KEYFILE} ${COMMIT}
-# Verify
-${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-file=${PUBKEYS} ${COMMIT}
-tap_ok "sign with ed25519 keys file"
-
-# Check the well-known places mechanism
-mkdir -p ${test_tmpdir}/{trusted,revoked}.ed25519.d
-for((i=0;i<100;i++)); do
-    # Generate some key files with random public signatures
-    gen_ed25519_random_public > ${test_tmpdir}/trusted.ed25519.d/signature_$i
-done
-# Check no valid public keys are available
-if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-dir=${test_tmpdir} ${COMMIT}; then
-    exit 1
-fi
-echo ${PUBLIC} > ${test_tmpdir}/trusted.ed25519.d/correct
-# Verify with correct key
-${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-dir=${test_tmpdir} ${COMMIT}
-
-tap_ok "verify ed25519 system-wide configuration"
-
-# Add the public key into revoked list
-echo ${PUBLIC} > ${test_tmpdir}/revoked.ed25519.d/correct
-# Check if public key is not valid anymore
-if ${CMD_PREFIX} ostree --repo=${test_tmpdir}/repo sign --verify --sign-type=ed25519 --keys-dir=${test_tmpdir} ${COMMIT}; then
-    exit 1
-fi
-rm -rf ${test_tmpdir}/{trusted,revoked}.ed25519.d
-tap_ok "verify ed25519 revoking keys mechanism"
-
-tap_end